from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementClickInterceptedException
from typing import Dict, Any, Optional, Tuple
import logging
import time
from enum import Enum
from .driver_manager import driver_manager, BrowserType
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
import traceback
from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementClickInterceptedException, ElementNotInteractableException
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class StepType(Enum):
    OPEN_URL = "open_url"
    CLICK = "click"
    INPUT_TEXT = "input_text"
    ASSERT_TEXT = "assert_text"
    ASSERT_ELEMENT = "assert_element"
    WAIT_FOR_ELEMENT = "wait_for_element"
    SELECT_OPTION = "select_option"
    SELECT_ELEMENTUI_OPTION = "select_elementui_option"
    EXECUTE_SCRIPT = "execute_script"

class StepStatus(Enum):
    PENDING = "pending"
    EXECUTING = "executing"
    SUCCESS = "success"
    FAILED = "failed"
    SKIPPED = "skipped"

class StepExecutor:
    """测试步骤执行器，负责解析和执行各种测试步骤"""
    def __init__(self):
        # 步骤处理器映射，使用策略模式
        self.step_handlers = {
            StepType.OPEN_URL: self._handle_open_url,
            StepType.CLICK: self._handle_click,
            StepType.INPUT_TEXT: self._handle_input_text,
            StepType.ASSERT_TEXT: self._handle_assert_text,
            StepType.ASSERT_ELEMENT: self._handle_assert_element,
            StepType.WAIT_FOR_ELEMENT: self._handle_wait_for_element,
            StepType.SELECT_OPTION: self._handle_select_option,
            StepType.SELECT_ELEMENTUI_OPTION: self._handle_select_elementui_option,
            StepType.EXECUTE_SCRIPT: self._handle_execute_script
        }
        
    def execute_step(self, driver_id: str, step: Dict[str, Any]) -> Dict[str, Any]:
        """
        执行单个测试步骤
        :param driver_id: 浏览器驱动ID
        :param step: 测试步骤字典
        :return: 执行结果
        """
        result = {
            "step_id": step.get("id", None),
            "type": step.get("type"),
            "status": StepStatus.EXECUTING.value,
            "start_time": time.time(),
            "end_time": None,
            "duration": None,
            "message": None,
            "screenshot": None
        }
        
        try:
            # 获取驱动实例
            driver = driver_manager.get_driver(driver_id)
            if not driver:
                raise ValueError(f"驱动实例不存在: {driver_id}")
            
            # 验证步骤类型
            step_type = step.get("type")
            if not step_type:
                raise ValueError("测试步骤缺少type字段")
            
            # 查找并执行对应的步骤处理器
            step_type_enum = StepType(step_type)
            handler = self.step_handlers.get(step_type_enum)
            if not handler:
                raise NotImplementedError(f"未实现的步骤类型: {step_type}")
            
            # 执行步骤
            handler_result = handler(driver, step)
            
            # 更新结果
            result["status"] = StepStatus.SUCCESS.value
            result["message"] = handler_result.get("message", "步骤执行成功")
            
        except Exception as e:
            # 捕获所有异常并记录
            result["status"] = StepStatus.FAILED.value
            result["message"] = str(e)
            # 打印完整堆栈
            error_detail = traceback.format_exc()
            print("步骤执行异常：", error_detail)
            # 失败时自动截图
            try:
                result["screenshot"] = driver_manager.take_screenshot(
                    driver_id, f"step_failed_{step.get('id', 'unknown')}")
            except Exception as screenshot_e:
                logger.warning(f"截图失败: {str(screenshot_e)}")
                
        finally:
            # 计算耗时
            result["end_time"] = time.time()
            result["duration"] = result["end_time"] - result["start_time"]
            
        return result
    
    def _handle_open_url(self, driver: WebDriver, step: Dict[str, Any]) -> Dict[str, str]:
        """处理打开URL步骤"""
        url = step.get("url")
        if not url:
            raise ValueError("open_url步骤缺少url参数")
            
        driver.get(url)
        logger.info(f"已打开URL: {url}")
        return {"message": f"成功打开URL: {url}"}
    
    def _handle_click(self, driver: WebDriver, step: Dict[str, Any]) -> Dict[str, str]:
        """处理点击元素步骤"""
        locator = self._get_locator(step)
        wait_timeout = step.get("wait_timeout", 10)
        
        # 等待元素可点击并点击
        element = WebDriverWait(driver, wait_timeout).until(
            EC.element_to_be_clickable(locator)
        )
        
        # 尝试点击，处理可能的覆盖层问题
        try:
            element.click()
        except ElementClickInterceptedException:
            # 使用JavaScript点击作为备选方案
            driver.execute_script("arguments[0].click();", element)
            
        logger.info(f"已点击元素: {locator}")
        return {"message": f"已点击元素: {locator}"}    
    
    def _handle_input_text(self, driver: WebDriver, step: Dict[str, Any]) -> Dict[str, str]:
        """处理输入文本步骤"""
        locator = self._get_locator(step)
        text = step.get("text")
        wait_timeout = step.get("wait_timeout", 10)

        if text is None:
            raise ValueError("input_text步骤缺少text参数")

        # 等待元素可见并输入文本
        element = WebDriverWait(driver, wait_timeout).until(
            EC.visibility_of_element_located(locator)
        )

        # 激活输入框，处理遮挡问题
        try:
            element.click()
        except ElementClickInterceptedException:
            # 遇到遮挡时用 JS 强制点击
            driver.execute_script("arguments[0].click();", element)

        element.clear()
        element.send_keys(text)
        # 触发前端事件（ElementUI/Vue/React等推荐）
        element.send_keys(Keys.TAB)
        driver.execute_script(
            "arguments[0].value = arguments[1]; arguments[0].dispatchEvent(new Event('input')); arguments[0].dispatchEvent(new Event('blur'));",
            element, text
        )

        logger.info(f"已在元素 {locator} 输入文本: {text}")
        return {"message": f"已输入文本: {text}"}
    
    def _handle_assert_text(self, driver: WebDriver, step: Dict[str, Any]) -> Dict[str, str]:
        """处理文本断言步骤"""
        locator = self._get_locator(step)
        expected_text = step.get("expected_text")
        wait_timeout = step.get("wait_timeout", 10)
        exact_match = step.get("exact_match", True)
        
        if expected_text is None:
            raise ValueError("assert_text步骤缺少expected_text参数")
            
        # 等待元素可见并获取文本
        element = WebDriverWait(driver, wait_timeout).until(
            EC.visibility_of_element_located(locator)
        )
        actual_text = element.text
        
        # 断言文本
        if exact_match:
            if actual_text != expected_text:
                raise AssertionError(f"文本不匹配: 预期 '{expected_text}', 实际 '{actual_text}'")
        else:
            if expected_text not in actual_text:
                raise AssertionError(f"文本不包含: 预期 '{expected_text}', 实际 '{actual_text}'")
        
        return {"message": f"文本断言成功: {expected_text}"}    
    
    def _handle_assert_element(self, driver: WebDriver, step: Dict[str, Any]) -> Dict[str, str]:
        """处理元素存在断言步骤"""
        locator = self._get_locator(step)
        wait_timeout = step.get("wait_timeout", 10)
        
        # 等待元素可见
        WebDriverWait(driver, wait_timeout).until(
            EC.visibility_of_element_located(locator)
        )
        
        return {"message": f"元素存在断言成功: {locator}"}    
    
    def _handle_wait_for_element(self, driver: WebDriver, step: Dict[str, Any]) -> Dict[str, str]:
        """处理等待元素步骤"""
        locator = self._get_locator(step)
        wait_timeout = step.get("timeout", 30)
        condition_type = step.get("condition", "visible")
        
        # 根据条件类型选择不同的等待条件
        condition_map = {
            "visible": EC.visibility_of_element_located,
            "present": EC.presence_of_element_located,
            "clickable": EC.element_to_be_clickable,
            "invisible": EC.invisibility_of_element_located
        }
        
        condition = condition_map.get(condition_type)
        if not condition:
            raise ValueError(f"不支持的等待条件: {condition_type}")
        
        # 执行等待
        WebDriverWait(driver, wait_timeout).until(condition(locator))
        
        return {"message": f"等待元素成功: {locator}, 条件: {condition_type}, 超时: {wait_timeout}s"}    
    
    def _handle_select_option(self, driver: WebDriver, step: Dict[str, Any]) -> Dict[str, str]:
        """处理选择下拉框选项步骤"""
        from selenium.webdriver.support.ui import Select
        
        locator = self._get_locator(step)
        option = step.get("option")
        by = step.get("by", "visible_text")
        wait_timeout = step.get("wait_timeout", 10)
        
        if not option:
            raise ValueError("select_option步骤缺少option参数")
            
        # 等待元素可见并获取select元素
        element = WebDriverWait(driver, wait_timeout).until(
            EC.visibility_of_element_located(locator)
        )
        select = Select(element)
        
        # 根据不同方式选择选项
        if by == "visible_text":
            select.select_by_visible_text(option)
        elif by == "value":
            select.select_by_value(option)
        elif by == "index":
            select.select_by_index(int(option))
        else:
            raise ValueError(f"不支持的选择方式: {by}")
        
        return {"message": f"已选择选项: {option}, 方式: {by}"}    
    
    
    # 新增方法
    def _handle_select_elementui_option(self, driver: WebDriver, step: Dict[str, Any]) -> Dict[str, str]:
        """
        处理 ElementUI 下拉选择框
        step: {
            "locator": { "by": "xpath", "value": "选择框xpath" },
            "option_text": "选项文本",
            "wait_timeout": 10
        }
        """
        locator = self._get_locator(step)
        option_text = step.get("option_text")
        wait_timeout = step.get("wait_timeout", 10)

        print(driver.page_source)  # 打印当前页面源码
        driver.save_screenshot("debug_select.png")  # 截图

        # 等待选择框可见
        WebDriverWait(driver, wait_timeout).until(
            EC.visibility_of_element_located(locator)
        )

        # 1. 点击选择框
        select_box = WebDriverWait(driver, wait_timeout).until(
            EC.element_to_be_clickable(locator)
        )
        if not select_box.is_displayed():
            raise Exception("定位到的选择框不可见，请检查XPath或页面状态")
        try:
            select_box.click()
        except ElementNotInteractableException:
            driver.execute_script("arguments[0].click();", select_box)

        # 2. 等待选项弹层出现并点击目标选项
        option_xpath = f"//span[text()='{option_text}']"
        WebDriverWait(driver, wait_timeout).until(
            EC.visibility_of_element_located((By.XPATH, option_xpath))
        )
        option_elem = driver.find_element(By.XPATH, option_xpath)
        option_elem.click()

        return {"message": f"已选择ElementUI下拉选项: {option_text}"}

    def _handle_execute_script(self, driver: WebDriver, step: Dict[str, Any]) -> Dict[str, str]:
        """处理执行JavaScript步骤"""
        script = step.get("script")
        if not script:
            raise ValueError("execute_script步骤缺少script参数")
            
        # 执行脚本
        result = driver.execute_script(script)
        
        return {"message": f"JavaScript执行成功, 返回结果: {str(result)}"}    
    
    def _get_locator(self, step: Dict[str, Any]) -> Tuple[str, str]:
        """从步骤中获取元素定位器
        返回格式: (by, value)
        """
        locator = step.get("locator")
        if not locator or not isinstance(locator, dict):
            raise ValueError("步骤缺少有效的locator字段")
        
        by = locator.get("by")
        value = locator.get("value")
        
        if not by or not value:
            raise ValueError("locator必须包含by和value字段")
        
        # 转换为Selenium的By类型
        from selenium.webdriver.common.by import By
        by_map = {
            "id": By.ID,
            "name": By.NAME,
            "class_name": By.CLASS_NAME,
            "css": By.CSS_SELECTOR,
            "xpath": By.XPATH,
            "link_text": By.LINK_TEXT,
            "partial_link_text": By.PARTIAL_LINK_TEXT,
            "tag_name": By.TAG_NAME
        }
        
        by_enum = by_map.get(by)
        if not by_enum:
            raise ValueError(f"不支持的定位方式: {by}")
        print("step locator:", locator)
        return (by_enum, value)
    
 

# 创建单例实例
executor = StepExecutor()